46129 frontend [ templates ] Add FieldSets#205
Conversation
…ets' into frontend/templates/46129__add_fieldsets
…and cloned kickoff test
| for field in self.instance.fields.all(): | ||
| if field.value not in self.NULL_VALUES: | ||
| total += float(field.value) | ||
| if total != float(self.instance.value): |
There was a problem hiding this comment.
🟡 Medium fieldsets/fieldset_rule.py:22
Floating-point comparison total != float(self.instance.value) incorrectly rejects valid field sets due to precision loss. For example, when fields sum to 0.3 via 0.1 + 0.2, the strict equality check fails because 0.1 + 0.2 != 0.3 in IEEE 754 arithmetic. Consider using a tolerance-based comparison like abs(total - float(self.instance.value)) > epsilon.
- if total != float(self.instance.value):
+ if abs(total - float(self.instance.value)) > 1e-9:🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file backend/src/processes/services/workflows/fieldsets/fieldset_rule.py around line 22:
Floating-point comparison `total != float(self.instance.value)` incorrectly rejects valid field sets due to precision loss. For example, when fields sum to `0.3` via `0.1 + 0.2`, the strict equality check fails because `0.1 + 0.2 != 0.3` in IEEE 754 arithmetic. Consider using a tolerance-based comparison like `abs(total - float(self.instance.value)) > epsilon`.
Evidence trail:
backend/src/processes/services/workflows/fieldsets/fieldset_rule.py lines 16-24 at REVIEWED_COMMIT - shows the `_validate_sum_equal` method with `total != float(self.instance.value)` comparison on line 22. IEEE 754 floating-point precision issues are well-documented (e.g., Python docs on floating point: https://docs.python.org/3/tutorial/floatingpoint.html).
| def _validate(self, **kwargs): | ||
| field_type = kwargs.get('type') | ||
|
|
There was a problem hiding this comment.
🟢 Low templates/field_template.py:19
In FieldTemplateService.partial_update, _validate checks field_type == FieldType.USER against kwargs.get('type'), but during partial updates type may not be provided. When updating only is_required=False on an existing USER field, field_type becomes None and the validation incorrectly passes, allowing a USER field to become non-required. Consider using self.instance.type as a fallback when type is not in update_kwargs.
def _validate(self, **kwargs):
- field_type = kwargs.get('type')
+ field_type = kwargs.get('type', self.instance.type if self.instance else None)
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file backend/src/processes/services/templates/field_template.py around lines 19-21:
In `FieldTemplateService.partial_update`, `_validate` checks `field_type == FieldType.USER` against `kwargs.get('type')`, but during partial updates `type` may not be provided. When updating only `is_required=False` on an existing `USER` field, `field_type` becomes `None` and the validation incorrectly passes, allowing a `USER` field to become non-required. Consider using `self.instance.type` as a fallback when `type` is not in `update_kwargs`.
Evidence trail:
backend/src/processes/services/templates/field_template.py lines 18-27 (_validate method), lines 33-40 (partial_update method), backend/src/generics/base/service.py lines 13-30 (BaseModelService.__init__ showing self.instance is set from constructor)
| def _validate(self, **kwargs): | ||
|
|
||
| """ Call after objects save """ | ||
|
|
||
| validator = getattr(self, f'_validate_{self.instance.type}', None) | ||
| validator(**kwargs) |
There was a problem hiding this comment.
🟢 Low fieldsets/fieldset_rule.py:37
In _validate, validator is fetched with getattr(..., None), but validator(**kwargs) is called unconditionally. When self.instance.type has no matching _validate_{type} method, this raises TypeError: 'NoneType' object is not callable instead of a clear validation error.
def _validate(self, **kwargs):
""" Call after objects save """
validator = getattr(self, f'_validate_{self.instance.type}', None)
- validator(**kwargs)
+ if validator is not None:
+ validator(**kwargs)Also found in 1 other location(s)
backend/src/processes/services/templates/fieldsets/fieldset.py:113
In
_validate_rules, callingservice._validate()will crash withTypeError: 'NoneType' object is not callablefor any rule whosetypedoes not have a corresponding_validate_{type}method. The referencedFieldsetTemplateRuleService._validate()method usesgetattr(self, f'_validate_{self.instance.type}', None)and then calls the result without checking if it'sNone. Only_validate_sum_equalexists in the visible code, so any rule with a different type will fail.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file backend/src/processes/services/templates/fieldsets/fieldset_rule.py around lines 37-42:
In `_validate`, `validator` is fetched with `getattr(..., None)`, but `validator(**kwargs)` is called unconditionally. When `self.instance.type` has no matching `_validate_{type}` method, this raises `TypeError: 'NoneType' object is not callable` instead of a clear validation error.
Evidence trail:
backend/src/processes/services/templates/fieldsets/fieldset_rule.py lines 37-38 at REVIEWED_COMMIT (getattr with None default, unconditional call)
backend/src/processes/enums.py lines 747-756 at REVIEWED_COMMIT (FieldSetRuleType only defines 'sum_equal')
backend/src/processes/models/mixins.py lines 349-359 at REVIEWED_COMMIT (BaseFieldSetRuleMixin defines type field with choices=FieldSetRuleType.CHOICES - application-level validation only)
Also found in 1 other location(s):
- backend/src/processes/services/templates/fieldsets/fieldset.py:113 -- In `_validate_rules`, calling `service._validate()` will crash with `TypeError: 'NoneType' object is not callable` for any rule whose `type` does not have a corresponding `_validate_{type}` method. The referenced `FieldsetTemplateRuleService._validate()` method uses `getattr(self, f'_validate_{self.instance.type}', None)` and then calls the result without checking if it's `None`. Only `_validate_sum_equal` exists in the visible code, so any rule with a different type will fail.
| } | ||
| """ | ||
|
|
||
| if data.get('fields'): |
There was a problem hiding this comment.
🟢 Low workflows/kickoff_version.py:180
When data contains {'fields': [], 'fieldsets': []}, the empty fields list is skipped due to truthiness check on line 180, but empty fieldsets is processed via is not None check on line 182. This causes _update_fieldsets to delete all fieldsets while _update_fields leaves orphaned fields intact — inconsistent behavior for empty list inputs. Consider changing line 180 to if data.get('fields') is not None: to match the fieldsets handling.
Also found in 1 other location(s)
backend/src/processes/services/tasks/task_version.py:66
If
field_data.get('selections')returns an empty list[], the condition on line 66 evaluates to falsy, so the method exits without deleting existing selections. This means when a field previously had selections but the new data has'selections': [], the old selections will incorrectly remain in the database instead of being cleared.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file backend/src/processes/services/workflows/kickoff_version.py around line 180:
When `data` contains `{'fields': [], 'fieldsets': []}`, the empty `fields` list is skipped due to truthiness check on line 180, but empty `fieldsets` is processed via `is not None` check on line 182. This causes `_update_fieldsets` to delete all fieldsets while `_update_fields` leaves orphaned fields intact — inconsistent behavior for empty list inputs. Consider changing line 180 to `if data.get('fields') is not None:` to match the fieldsets handling.
Evidence trail:
backend/src/processes/services/workflows/kickoff_version.py lines 180-183 (condition checks), lines 63-77 (_update_fields with delete at line 76-78), lines 136-163 (_update_fieldsets with delete at lines 161-163). Verified at REVIEWED_COMMIT.
Also found in 1 other location(s):
- backend/src/processes/services/tasks/task_version.py:66 -- If `field_data.get('selections')` returns an empty list `[]`, the condition on line 66 evaluates to falsy, so the method exits without deleting existing selections. This means when a field previously had selections but the new data has `'selections': []`, the old selections will incorrectly remain in the database instead of being cleared.
…aticapp/pneumaticworkflow into frontend/templates/46129__add_fieldsets
…off and FieldSetTemplate models
…ntermediate m2m models
…aticapp/pneumaticworkflow into frontend/templates/46129__add_fieldsets
| selection_ids.add(selection.id) | ||
| field.selections.exclude(id__in=selection_ids).delete() | ||
| self._update_field_selections(field, field_data) | ||
| self.instance.output.exclude(id__in=field_ids).delete() |
There was a problem hiding this comment.
🟠 High tasks/task_version.py:94
In _update_fields, the deletion at line 94 removes ALL TaskField objects with task=self.instance, including fields that belong to fieldsets. Since _update_fields runs before _update_fieldsets (lines 531-532), and field_ids only tracks non-fieldset fields, existing fieldset fields are deleted before _update_fieldsets can recreate them. The deletion should filter to only remove fields without a fieldset using self.instance.output.filter(fieldset__isnull=True).exclude(id__in=field_ids).delete().
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file backend/src/processes/services/tasks/task_version.py around line 94:
In `_update_fields`, the deletion at line 94 removes ALL `TaskField` objects with `task=self.instance`, including fields that belong to fieldsets. Since `_update_fields` runs before `_update_fieldsets` (lines 531-532), and `field_ids` only tracks non-fieldset fields, existing fieldset fields are deleted before `_update_fieldsets` can recreate them. The deletion should filter to only remove fields without a fieldset using `self.instance.output.filter(fieldset__isnull=True).exclude(id__in=field_ids).delete()`.
Evidence trail:
backend/src/processes/services/tasks/task_version.py lines 82-94 (_update_fields: field_ids only collects non-fieldset fields, then deletes all output not in field_ids), lines 91 (fieldset=None), line 94 (self.instance.output.exclude(id__in=field_ids).delete()), lines 531-532 (_update_fields called before _update_fieldsets), lines 167-187 (_update_field uses TaskField.objects.update_or_create with fieldset in lookup), lines 231-244 (_update_fieldset_fields), lines 246-276 (_update_fieldsets). backend/src/processes/models/workflows/fields.py lines 47-53 (TaskField.task FK with related_name='output'), lines 60-65 (TaskField.fieldset FK nullable), line 76 (objects = BaseSoftDeleteManager). backend/src/generics/managers.py lines 6-8 (BaseSoftDeleteManager filters is_deleted=False). backend/src/generics/querysets.py lines 59-61 (BaseQuerySet.delete does soft delete via update is_deleted=True).
| def _update_fieldsets(self, data: Optional[List]) -> None: | ||
|
|
||
| fieldset_api_names = set() | ||
| for fieldset_data in data or []: | ||
| task_link = next( | ||
| link for link in fieldset_data['task_links'] | ||
| if link['task_api_name'] == self.instance.api_name | ||
| ) | ||
| order = task_link['order'] |
There was a problem hiding this comment.
🟡 Medium tasks/task_version.py:246
In _update_fieldsets, the next() call at line 250 raises StopIteration if no task_link matches self.instance.api_name, causing the entire update operation to fail. Consider providing a default value with None and handling the missing case gracefully.
fieldset_api_names = set()
for fieldset_data in data or []:
- task_link = next(
- link for link in fieldset_data['task_links']
- if link['task_api_name'] == self.instance.api_name
- )
- order = task_link['order']
+ task_link = next(
+ (link for link in fieldset_data['task_links']
+ if link['task_api_name'] == self.instance.api_name),
+ None
+ )
+ if task_link is None:
+ continue
+ order = task_link['order']
fieldset, _ = FieldSet.objects.update_or_create(🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file backend/src/processes/services/tasks/task_version.py around lines 246-254:
In `_update_fieldsets`, the `next()` call at line 250 raises `StopIteration` if no `task_link` matches `self.instance.api_name`, causing the entire update operation to fail. Consider providing a default value with `None` and handling the missing case gracefully.
Evidence trail:
backend/src/processes/services/tasks/task_version.py lines 246-253 (the `next()` call without default), lines 498-532 (`update_from_version` calling `_update_fieldsets`), backend/src/processes/services/versioning/schemas.py lines 84-93 (FieldsetTemplateTaskTemplateSchemaV1), lines 100-135 (FieldSetSchemaV1 with task_links), lines 266-295 (TaskSchemaV1 with fieldsets), backend/src/processes/services/workflows/workflow_version.py lines 61-80 (_update_tasks_from_version passing data).
| service.validate_rules() | ||
| except FieldsetServiceException as ex: | ||
| self.raise_validation_error(message=ex.message) | ||
| for field_template in kickoff.fields.filter(fieldset__isnull=True): |
There was a problem hiding this comment.
🟢 Low workflows/kickoff_value.py:113
When creating fields without a fieldset at lines 113-120, fieldset_id is not passed to TaskFieldService.create(). If the field template has rules, _link_rules accesses kwargs['fieldset_id'] with direct key access and raises KeyError, which escapes the except (TaskFieldException, FieldsetServiceException) block at line 121. Pass fieldset_id=None for fields without a fieldset.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file backend/src/processes/serializers/workflows/kickoff_value.py around line 113:
When creating fields without a fieldset at lines 113-120, `fieldset_id` is not passed to `TaskFieldService.create()`. If the field template has rules, `_link_rules` accesses `kwargs['fieldset_id']` with direct key access and raises `KeyError`, which escapes the `except (TaskFieldException, FieldsetServiceException)` block at line 121. Pass `fieldset_id=None` for fields without a fieldset.
Evidence trail:
backend/src/processes/serializers/workflows/kickoff_value.py lines 113-121 (REVIEWED_COMMIT): `service.create()` called without `fieldset_id`; except block catches only `TaskFieldException, FieldsetServiceException`.
backend/src/generics/base/service.py lines 65-70: `BaseModelService.create(**kwargs)` passes kwargs to `_create_related(**kwargs)`.
backend/src/processes/services/tasks/field.py line 326: `_create_related` calls `_link_rules(instance_template, **kwargs)` if rules exist.
backend/src/processes/services/tasks/field.py line 375: `_link_rules` uses `kwargs['fieldset_id']` (direct key access, raises KeyError if missing).
backend/src/processes/models/templates/fields.py lines 59-68: `FieldTemplate.fieldset` is nullable, and `rules` M2M has no constraint preventing association on fieldset-less fields.
| if field.value not in self.NULL_VALUES: | ||
| total += float(field.value) | ||
| if total != float(self.instance.value): | ||
| raise FieldsetServiceException(MSG_FS_0002(self.instance.value)) |
There was a problem hiding this comment.
Floating-point equality check in sum validation is unreliable
Medium Severity
_validate_sum_equal accumulates field values using float() addition and then compares the total with exact equality (!=). Floating-point arithmetic can produce rounding errors (e.g., 0.1 + 0.2 != 0.3), causing valid inputs to fail validation spuriously. Using Decimal or an epsilon-based comparison would be more reliable.
Reviewed by Cursor Bugbot for commit 5944362. Configure here.
…sorted list across workflow log, task log and kickoff of workflow modal
| from django.db import models # noqa : PLC0415 | ||
| if isinstance(instance, models.Manager): | ||
| instance = instance.first() | ||
| return super().to_representation(instance) |
There was a problem hiding this comment.
KickoffListSerializer crashes when no kickoff exists
Medium Severity
KickoffListSerializer.to_representation handles Manager instances by calling .first() but doesn't check for None afterward. If no kickoff exists, super().to_representation(None) will crash. The sibling KickoffSerializer correctly handles this case by returning {'fields': [], 'fieldsets': []} when instance is None, but KickoffListSerializer omits that guard.
Reviewed by Cursor Bugbot for commit 15db8dd. Configure here.
…, show skeleton only during loading, add FieldsetDetails tests
…to backend/fieldsets/45773__fieldsets
…mpleted_or_skipped" for backward compatibility
…aticapp/pneumaticworkflow into frontend/templates/46129__add_fieldsets
…ync on catalog change, and TemplateControlls fieldsets flows
…, task output, runtime, clone)
| order=kwargs['order'], | ||
| label_position=instance_template.label_position, | ||
| layout=instance_template.layout, | ||
| ) |
There was a problem hiding this comment.
Fieldset _create_instance missing account_id assignment
Medium Severity
FieldSetService._create_instance passes account=self.account when creating a FieldSet, but the service receives account_id as a kwarg (not a user-based account). When called from create_fieldsets_from_template or KickoffValueSerializer, self.account may be None because BaseModelService.__init__ only sets self.account from user.account — yet the service is often instantiated with only user, while account_id is passed separately via kwargs but never used in _create_instance.
Reviewed by Cursor Bugbot for commit 5588c69. Configure here.
1. Add deafault.env 2. Add SSL options to the install.sh 3. Add all environment variables to the docker compose files
… highlights and page title
| is_superuser=self.is_superuser, | ||
| auth_type=self.auth_type, | ||
| ) | ||
| service.validate_rules() |
There was a problem hiding this comment.
Fieldset rule violations swallowed as uncaught exception during task completion
High Severity
service.validate_rules() is called inside complete_task with no surrounding try/except. FieldSetService.validate_rules() raises FieldsetServiceException when a rule (e.g. SUM_EQUAL) is violated. Without a handler, the exception propagates uncaught through the transaction.atomic() block, rolling back the transaction and surfacing as a 500 error instead of a proper validation response. The parallel path in KickoffValueSerializer.update correctly wraps the same call in try/except FieldsetServiceException.
Reviewed by Cursor Bugbot for commit 3e05694. Configure here.
…Modal, TuneViewModal and logs
|
|
||
| fs_api_names = set() | ||
| for fs_data in data or []: | ||
| order = fs_data['kickoff_links'][0]['order'] |
There was a problem hiding this comment.
Empty kickoff links crashes version sync
Medium Severity
_update_fieldsets sets order from fs_data['kickoff_links'][0]['order'] without checking that kickoff_links is non-empty. Version payload with a fieldset and no kickoff link entries causes an IndexError during workflow template updates.
Reviewed by Cursor Bugbot for commit 5e2e65a. Configure here.
| pass | ||
| fieldsets_api_names.append(elem['fieldset']['api_name']) | ||
| except (TypeError, ValueError): | ||
| continue |
There was a problem hiding this comment.
Embed draft misses fieldset fields
Medium Severity
_get_raw_fields_from_kickoff reads kickoff fieldset links from fieldsettemplatekickoff_set and expects each entry to nest the api name under fieldset, while draft normalization stores fieldsets with top-level api_name. Required-field metadata for embed/public flows can omit fieldset member fields.
Reviewed by Cursor Bugbot for commit 5e2e65a. Configure here.
…ew, TemplateLayout
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
There are 11 total unresolved issues (including 8 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit cff7011. Configure here.
| 'order': link['order'], | ||
| } | ||
| for link in fieldsets_raw | ||
| ] |
There was a problem hiding this comment.
Kickoff fieldset links not saved
High Severity
Template create/update reads kickoff and task fieldset links from fieldsettemplatekickoff_set / fieldsettemplatetasktemplate_set, but DRF validated payloads use the serializer field name fieldsets. The pop always yields nothing, so create_or_update_kickoff_links and per-task tasks_fieldsets never run and fieldset associations on kickoff/tasks are not persisted when saving a template.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit cff7011. Configure here.
| 'api_name': link['fieldset']['api_name'], | ||
| 'order': link['order'], | ||
| } | ||
| for link in fieldsets_links_raw |
There was a problem hiding this comment.
Wrong api_name in link parsing
High Severity
When kickoff/task fieldset links are present, the code reads link['fieldset']['api_name'], but FieldsetTemplateKickoffSerializer / FieldsetTemplateTaskTemplateSerializer validate each link as { order, api_name }. Saving templates with fieldsets would raise KeyError or skip names even after fixing the pop key.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit cff7011. Configure here.
| fieldset_fields = FieldTemplate.objects.filter( | ||
| fieldset__api_name__in=fieldsets_api_names, | ||
| account_id=account.id, | ||
| ) |
There was a problem hiding this comment.
Wf name ignores kickoff fieldsets
Medium Severity
_get_raw_fields_from_kickoff only collects fieldset API names from fieldsettemplatekickoff_set entries shaped as fieldset.api_name, and loads fields without scoping to the template being saved. Kickoff fieldsets sent as fieldsets are ignored, so wf_name_template validation can reject valid placeholders or pull fields from another template with the same fieldset api_name.
Reviewed by Cursor Bugbot for commit cff7011. Configure here.
…, mappers and validators
…umaticapp/pneumaticworkflow into frontend/templates/46129__add_fieldsets
| echo "" | ||
|
|
||
|
|
||
| # 2.2 Stop running containers |
There was a problem hiding this comment.
🟢 Low start.sh:207
set -e at line 4 causes the script to exit immediately when the docker compose command inside the command substitution fails at lines 217 or 219. The non-zero exit status from the command substitution triggers set -e before the if [ $? -eq 0 ] check on line 221 is reached, so the error-handling block (lines 221-226) is dead code. On failure, the user sees silent exit with no error message instead of the intended print_error "$output". The same applies to line 209 where a docker compose down failure also silently kills the script. Consider disabling set -e for these commands or using a different error-handling pattern that doesn't rely on checking $? after a command substitution.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file start.sh around line 207:
`set -e` at line 4 causes the script to exit immediately when the `docker compose` command inside the command substitution fails at lines 217 or 219. The non-zero exit status from the command substitution triggers `set -e` before the `if [ $? -eq 0 ]` check on line 221 is reached, so the error-handling block (lines 221-226) is dead code. On failure, the user sees silent exit with no error message instead of the intended `print_error "$output"`. The same applies to line 209 where a `docker compose down` failure also silently kills the script. Consider disabling `set -e` for these commands or using a different error-handling pattern that doesn't rely on checking `$?` after a command substitution.
| sed -i "s|^#\?\s*POSTGRES_REPLICA_PASSWORD=.*|POSTGRES_REPLICA_PASSWORD=$POSTGRES_PASSWORD|" "$ENV_FILE" | ||
| sed -i "s|^#\?\s*REDIS_PASSWORD=.*|REDIS_PASSWORD=$REDIS_PASSWORD|" "$ENV_FILE" | ||
| sed -i "s|^#\?\s*RABBITMQ_PASSWORD=.*|RABBITMQ_PASSWORD=$RABBITMQ_PASSWORD|" "$ENV_FILE" | ||
| fi |
There was a problem hiding this comment.
🟡 Medium start.sh:100
When SERVER_ADDRESS is localhost, the NGINX_CONF_TEMPLATE variable is set to ./nginx/templates/ on line 39 but is never written to .env because the sed command on line 142 is inside the ADDRESS_IS_LOCALHOST = false block. The commented-out value in default.env points to ./nginx/ssl_templates/, so NGINX receives no valid template configuration for localhost deployments.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file start.sh around line 100:
When `SERVER_ADDRESS` is `localhost`, the `NGINX_CONF_TEMPLATE` variable is set to `./nginx/templates/` on line 39 but is never written to `.env` because the `sed` command on line 142 is inside the `ADDRESS_IS_LOCALHOST = false` block. The commented-out value in `default.env` points to `./nginx/ssl_templates/`, so NGINX receives no valid template configuration for localhost deployments.
…umaticapp/pneumaticworkflow into frontend/templates/46129__add_fieldsets
…r custom form domain
…umaticapp/pneumaticworkflow into frontend/templates/46129__add_fieldsets


Note
High Risk
Large schema migration plus workflow/template/condition and task-completion paths; misconfiguration of SERVER_ADDRESS or predicate migration could break hosts or start-task logic.
Overview
This PR adds reusable fieldsets on templates and workflows: grouped fields with layout/label options, M2M links to kickoff and tasks, and
sum_equalrules validated at kickoff update and task completion. Template and workflow APIs now exposefieldsetsalongside flatfields; output-field resolution includes fields inside linked fieldsets.Task conditions gain
skippedandcompleted_or_skippedoperators (with a data migration rewriting existing taskcompletedpredicates tocompleted_or_skipped).start_taskconditions may use any of the three completion-style operators.Supporting changes: dedicated
FieldSetTemplateService/ workflowFieldSetService,RelatedApiNameFieldfor slug lookups, defaultdelete()onBaseModelService, and template versioning schemas extended for fieldsets.Ops/docs: root README uses
SERVER_ADDRESSinstead ofFRONTEND_DOMAIN/BACKEND_DOMAIN; Celery compose services mirror more optional integration env vars and useSERVER_ADDRESSinALLOWED_HOSTS.Reviewed by Cursor Bugbot for commit 16adbfe. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add FieldSets to templates and workflows for grouping task and kickoff fields
FieldsetTemplate,FieldSet, and related rule/link models to group fields on template kickoffs, template tasks, workflow kickoffs, and workflow tasks with layout, label position, and validation rules (e.g.sum_equal).GET/POST /templates/{id}/fieldsets,GET/PATCH/DELETE /templates/fieldsets/{id}) viaFieldsetTemplateViewSetand a dedicatedFieldSetTemplateServicewith transactional create/update/delete of nested fields and rules.get_kickoff_fields/get_task_output_fieldsto include fieldset-associated fields alongside direct fields.SKIPPED,COMPLETED_OR_SKIPPED) for task conditions, updatesget_tasks_parentsandTaskResolverto handle them, and exposes them in the frontend condition UI./templates/:id/fieldsets/) with list, detail, create, and delete views (FieldsetCard,FieldsetDetails,FieldsetModal), Redux slice, sagas, and selectors.ExtraFieldIntlrendering inTaskCard,WorkflowEditPopup,KickoffRedux, andKickoffEditwith a unifiedMergedOutputList/OutputFormTaskMergedthat interleaves fields and fieldsets ordered byorder.TuneViewModal,FeedItemHeader,KickoffOutputs, andWorkflowLogTaskCompleteto display and count fieldset-grouped fields.start.shto prompt forSERVER_ADDRESS, generate secrets, and usedefault.env; nginx configuration is refactored into reusable includes undernginx/includes/.storageOutputslocalStorage format changed (outputkey →data), so any existing browser-stored task drafts will not be restored after this release.Macroscope summarized 16adbfe.